
/*
 * fuzzystrmatch.c
 *
 * 字符串的“模糊”比较函数
 *
 * Joe Conway <mail@joeconway.com>
 *
 * contrib/fuzzystrmatch/fuzzystrmatch.c
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 * ALL RIGHTS RESERVED;
 *
 * metaphone()
 * -----------
 * 由 Joe Conway 为 PostgreSQL 修改。
 * 基于 Michael G Schwern 的 CPAN “Text-Metaphone-1.96”
 * 代码经过轻微修改以用于 PostgreSQL 函数 (palloc, elog 等)。
 * Metaphone 最初由 Lawrence Philips 创建，并在 1990 年 12 月的
 * “Computer Language” 杂志中发表。
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written agreement
 * is hereby granted, provided that the above copyright notice and this
 * paragraph and the following two paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 */

#include "postgres.h"

#include <ctype.h>

#include "mb/pg_wchar.h"
#include "utils/builtins.h"
#include "utils/varlena.h"

PG_MODULE_MAGIC;

/*
 * Soundex
 */
static void fc__soundex(const char *fc_instr, char *fc_outstr);

#define SOUNDEX_LEN 4

/*									ABCDEFGHIJKLMNOPQRSTUVWXYZ */
static const char *soundex_table = "01230120022455012623010202";

static char fc_soundex_code(char fc_letter)
{
	fc_letter = toupper((unsigned char) fc_letter);
	/* 防御非ASCII字母 */
	if (fc_letter >= 'A' && fc_letter <= 'Z')
		return soundex_table[fc_letter - 'A'];
	return fc_letter;
}

/*
 * Metaphone
 */
#define MAX_METAPHONE_STRLEN		255

/*
 * 原代码由Michael G Schwern开始。
 * 代码稍作修改以用于PostgreSQL函数。
 */


/**************************************************************************
	metaphone -- 将英语短语分解成其音素。

	输入
		word			--	一个需要音素化的英语单词
		max_phonemes	--	要计算的音素数。如果为0，则会
							音素化整个短语。
		phoned_word		--	最终的音素化单词。（我们将分配
							内存。）
	输出
		error	--	一个简单的错误标志，返回真或假

	备注：所有非字母字符都被忽略，包括空格，
	尽管非字母字符会打断音素。
****************************************************************************/


/*	我对传统的metaphone算法进行了修改，
	你可能在书中找到这一算法。如果你想让metaphone表现
	得更传统，请定义此项 */
#undef USE_TRADITIONAL_METAPHONE

/* 特殊编码 */
#define  SH		'X'
#define  TH		'0'

static char fc_Lookahead(char *fc_word, int fc_how_far);
static void fc__metaphone(char *fc_word, int fc_max_phonemes, char **fc_phoned_word);

/* Metachar.h ... 与metaphone有关的字符小知识 */


/*-- 字符编码数组及访问宏 --*/
/* 直接从书中抄来的... */
static const char _codes[26] = {
	1, 16, 4, 16, 9, 2, 4, 16, 9, 2, 0, 2, 2, 2, 1, 4, 0, 2, 4, 4, 1, 0, 0, 0, 8, 0
/*	a  b c	d e f g  h i j k l m n o p q r s t u v w x y z */
};

static int fc_getcode(char fc_c)
{
	if (isalpha((unsigned char) fc_c))
	{
		fc_c = toupper((unsigned char) fc_c);
		/* 防御非ASCII字母 */
		if (fc_c >= 'A' && fc_c <= 'Z')
			return _codes[fc_c - 'A'];
	}
	return 0;
}

#define isvowel(c)	(fc_getcode(c) & 1)	/* AEIOU */

/* 这些字母保持不变 */
#define NOCHANGE(c) (getcode(c) & 2)	/* FJMNR */

/* 当在H前面时形成二合元音 */
#define AFFECTH(c)	(fc_getcode(c) & 4)	/* CGPST */

/* 这些使C和G变软 */
#define MAKESOFT(c) (fc_getcode(c) & 8)	/* EIY */

/* 这些防止GH变成F */
#define NOGHTOF(c)	(fc_getcode(c) & 16)	/* BDH */

PG_FUNCTION_INFO_V1(levenshtein_with_costs);
Datum levenshtein_with_costs(PG_FUNCTION_ARGS)
{
	text	   *fc_src = PG_GETARG_TEXT_PP(0);
	text	   *fc_dst = PG_GETARG_TEXT_PP(1);
	int			fc_ins_c = PG_GETARG_INT32(2);
	int			fc_del_c = PG_GETARG_INT32(3);
	int			fc_sub_c = PG_GETARG_INT32(4);
	const char *fc_s_data;
	const char *fc_t_data;
	int			fc_s_bytes,
				fc_t_bytes;

	/* 提取指向实际字符数据的指针 */
	fc_s_data = VARDATA_ANY(fc_src);
	fc_t_data = VARDATA_ANY(fc_dst);
	/* 确定每个字符串的字节长度 */
	fc_s_bytes = VARSIZE_ANY_EXHDR(fc_src);
	fc_t_bytes = VARSIZE_ANY_EXHDR(fc_dst);

	PG_RETURN_INT32(varstr_levenshtein(fc_s_data, fc_s_bytes, fc_t_data, fc_t_bytes,
									   fc_ins_c, fc_del_c, fc_sub_c, false));
}


PG_FUNCTION_INFO_V1(levenshtein);
Datum levenshtein(PG_FUNCTION_ARGS)
{
	text	   *fc_src = PG_GETARG_TEXT_PP(0);
	text	   *fc_dst = PG_GETARG_TEXT_PP(1);
	const char *fc_s_data;
	const char *fc_t_data;
	int			fc_s_bytes,
				fc_t_bytes;

	/* 提取指向实际字符数据的指针 */
	fc_s_data = VARDATA_ANY(fc_src);
	fc_t_data = VARDATA_ANY(fc_dst);
	/* 确定每个字符串的字节长度 */
	fc_s_bytes = VARSIZE_ANY_EXHDR(fc_src);
	fc_t_bytes = VARSIZE_ANY_EXHDR(fc_dst);

	PG_RETURN_INT32(varstr_levenshtein(fc_s_data, fc_s_bytes, fc_t_data, fc_t_bytes,
									   1, 1, 1, false));
}


PG_FUNCTION_INFO_V1(levenshtein_less_equal_with_costs);
Datum levenshtein_less_equal_with_costs(PG_FUNCTION_ARGS)
{
	text	   *fc_src = PG_GETARG_TEXT_PP(0);
	text	   *fc_dst = PG_GETARG_TEXT_PP(1);
	int			fc_ins_c = PG_GETARG_INT32(2);
	int			fc_del_c = PG_GETARG_INT32(3);
	int			fc_sub_c = PG_GETARG_INT32(4);
	int			fc_max_d = PG_GETARG_INT32(5);
	const char *fc_s_data;
	const char *fc_t_data;
	int			fc_s_bytes,
				fc_t_bytes;

	/* 提取指向实际字符数据的指针 */
	fc_s_data = VARDATA_ANY(fc_src);
	fc_t_data = VARDATA_ANY(fc_dst);
	/* 确定每个字符串的字节长度 */
	fc_s_bytes = VARSIZE_ANY_EXHDR(fc_src);
	fc_t_bytes = VARSIZE_ANY_EXHDR(fc_dst);

	PG_RETURN_INT32(varstr_levenshtein_less_equal(fc_s_data, fc_s_bytes,
												  fc_t_data, fc_t_bytes,
												  fc_ins_c, fc_del_c, fc_sub_c,
												  fc_max_d, false));
}


PG_FUNCTION_INFO_V1(levenshtein_less_equal);
Datum levenshtein_less_equal(PG_FUNCTION_ARGS)
{
	text	   *fc_src = PG_GETARG_TEXT_PP(0);
	text	   *fc_dst = PG_GETARG_TEXT_PP(1);
	int			fc_max_d = PG_GETARG_INT32(2);
	const char *fc_s_data;
	const char *fc_t_data;
	int			fc_s_bytes,
				fc_t_bytes;

	/* 提取指向实际字符数据的指针 */
	fc_s_data = VARDATA_ANY(fc_src);
	fc_t_data = VARDATA_ANY(fc_dst);
	/* 确定每个字符串的字节长度 */
	fc_s_bytes = VARSIZE_ANY_EXHDR(fc_src);
	fc_t_bytes = VARSIZE_ANY_EXHDR(fc_dst);

	PG_RETURN_INT32(varstr_levenshtein_less_equal(fc_s_data, fc_s_bytes,
												  fc_t_data, fc_t_bytes,
												  1, 1, 1,
												  fc_max_d, false));
}


/*
 * 计算输入字符串的metaphone。
 * 返回请求的字符数
 * （建议值为4）
 */
PG_FUNCTION_INFO_V1(metaphone);
Datum metaphone(PG_FUNCTION_ARGS)
{
	char	   *fc_str_i = TextDatumGetCString(PG_GETARG_DATUM(0));
	size_t		fc_str_i_len = strlen(fc_str_i);
	int			fc_reqlen;
	char	   *fc_metaph;

	/* 如果我们收到一个空字符串，则返回一个空字符串 */
	if (!(fc_str_i_len > 0))
		PG_RETURN_TEXT_P(cstring_to_text(""));

	if (fc_str_i_len > MAX_METAPHONE_STRLEN)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("argument exceeds the maximum length of %d bytes",
						MAX_METAPHONE_STRLEN)));

	fc_reqlen = PG_GETARG_INT32(1);
	if (fc_reqlen > MAX_METAPHONE_STRLEN)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("output exceeds the maximum length of %d bytes",
						MAX_METAPHONE_STRLEN)));

	if (!(fc_reqlen > 0))
		ereport(ERROR,
				(errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
				 errmsg("output cannot be empty string")));

	fc__metaphone(fc_str_i, fc_reqlen, &fc_metaph);
	PG_RETURN_TEXT_P(cstring_to_text(fc_metaph));
}


/*
 * 原代码由Michael G Schwern开始。
 * 代码稍作修改以用于PostgreSQL
 * 函数（palloc等）。
 */

/* 我想我本可以使用字符指针而不是
 * 直接访问数组... */

/* 查看单词中的下一个字母 */
#define Next_Letter (toupper((unsigned char) fc_word[fc_w_idx+1]))
/* 查看单词中的当前字母 */
#define Curr_Letter (toupper((unsigned char) fc_word[fc_w_idx]))
/* 向后移动N个字母。 */
#define Look_Back_Letter(n) \
	(fc_w_idx >= (n) ? toupper((unsigned char) fc_word[fc_w_idx-(n)]) : '\0')
/* 上一个字母。我不知道，如果失败，是否应该返回null？ */
#define Prev_Letter (Look_Back_Letter(1))
/* 向下看两个字母。它确保你不会超出字符串。 */
#define After_Next_Letter \
	(Next_Letter != '\0' ? toupper((unsigned char) fc_word[fc_w_idx+2]) : '\0')
#define Look_Ahead_Letter(n) toupper((unsigned char) fc_Lookahead(fc_word+fc_w_idx, n))


/* 允许我们安全地向前查看任意数量的字母 */
/* 我可能只是可以使用strlen... */
static char fc_Lookahead(char *fc_word, int fc_how_far)
{
	char		fc_letter_ahead = '\0';	/* 默认空 */
	int			fc_idx;

	for (fc_idx = 0; fc_word[fc_idx] != '\0' && fc_idx < fc_how_far; fc_idx++);
	/* 在字符串中向前推进... */

	fc_letter_ahead = fc_word[fc_idx];	/* idx 将等于 how_far 或字符串的末尾 */
	return fc_letter_ahead;
}


/* 对一个字母进行音素化 */
#define Phonize(c)	do {(*fc_phoned_word)[fc_p_idx++] = c;} while (0)
/* 在被音素化的单词末尾加上一个空字符 */
#define End_Phoned_Word do {(*fc_phoned_word)[fc_p_idx] = '\0';} while (0)
/* 被音素化的单词有多长？ */
#define Phone_Len	(fc_p_idx)

/* 注意字母是否是单词中的一个“断点” */
#define Isbreak(c)	(!isalpha((unsigned char) (c)))


static void fc__metaphone(char *fc_word,			/* 在 */
		   int fc_max_phonemes,
		   char **fc_phoned_word)	/* 出 */
{
	int			fc_w_idx = 0;		/* 我们在音素化的哪个点。 */
	int			fc_p_idx = 0;		/* 被音素化短语的结尾 */

	/*-- 参数检查 --*/

	/*
	 * 这可能是不必要的，但我还是保留了这些 jec 2001年8月3日
	 */

	/* 负音素长度毫无意义 */
	if (!(fc_max_phonemes > 0))
		/* 内部错误 */
		elog(ERROR, "metaphone: Requested output length must be > 0");

	/* 空/空字符串毫无意义 */
	if ((fc_word == NULL) || !(strlen(fc_word) > 0))
		/* 内部错误 */
		elog(ERROR, "metaphone: Input string length must be > 0");

	/*-- 为我们被音素化的短语分配内存 --*/
	if (fc_max_phonemes == 0)
	{							/* 假设最大可能 */
		*fc_phoned_word = palloc(sizeof(char) * strlen(fc_word) + 1);
	}
	else
	{
		*fc_phoned_word = palloc(sizeof(char) * fc_max_phonemes + 1);
	}

	/*-- 第一个音素必须特殊处理。 --*/
	/* 找到我们的第一个字母 */
	for (; !isalpha((unsigned char) (Curr_Letter)); fc_w_idx++)
	{
		/* 假设我们给的只是垃圾... */
		if (Curr_Letter == '\0')
		{
			End_Phoned_Word;
			return;
		}
	}

	switch (Curr_Letter)
	{
			/* AE 变为 E */
		case 'A':
			if (Next_Letter == 'E')
			{
				Phonize('E');
				fc_w_idx += 2;
			}
			/* 记住，保留开头的元音 */
			else
			{
				Phonize('A');
				fc_w_idx++;
			}
			break;
			/* [GKP]N 变为 N */
		case 'G':
		case 'K':
		case 'P':
			if (Next_Letter == 'N')
			{
				Phonize('N');
				fc_w_idx += 2;
			}
			break;

			/*
			 * WH 变为 H，WR 变为 R，如果后面跟着元音则为 W
			 */
		case 'W':
			if (Next_Letter == 'H' ||
				Next_Letter == 'R')
			{
				Phonize(Next_Letter);
				fc_w_idx += 2;
			}
			else if (isvowel(Next_Letter))
			{
				Phonize('W');
				fc_w_idx += 2;
			}
			/* 否则忽略 */
			break;
			/* X 变为 S */
		case 'X':
			Phonize('S');
			fc_w_idx++;
			break;
			/* 元音被保留 */

			/*
			 * 我们已经处理了 A case 'A': case 'a':
			 */
		case 'E':
		case 'I':
		case 'O':
		case 'U':
			Phonize(Curr_Letter);
			fc_w_idx++;
			break;
		default:
			/* 不执行任何操作 */
			break;
	}



	/* 继续元音化处理 */
	for (; Curr_Letter != '\0' &&
		 (fc_max_phonemes == 0 || Phone_Len < fc_max_phonemes);
		 fc_w_idx++)
	{
		/*
		 * 由于早期编码处理了多个字母，需要跳过多少个字母
		 */
		unsigned short int fc_skip_letter = 0;


		/*
		 * 想法：如果能做到比其他情况更好，比如...
		 * 好吧，SCI。对于 SCI，你编码 S，然后必须记得跳过
		 * C。所以音素 SCI 同时影响 S 和 C。更好的是，按照我
		 * 的想法，跳过编码中 S 部分的 C。见鬼，我试试。
		 */

		/* 忽略非字母字符 */
		if (!isalpha((unsigned char) (Curr_Letter)))
			continue;

		/* 删除重复项，除了 CC */
		if (Curr_Letter == Prev_Letter &&
			Curr_Letter != 'C')
			continue;

		switch (Curr_Letter)
		{
				/* B -> B 除非在 MB 中 */
			case 'B':
				if (Prev_Letter != 'M')
					Phonize('B');
				break;

				/*
				 * 'sh' 如果在 -CIA- 或 -CH，除了 SCH，除非 SCHW。 (SCHW 在 S 中处理) 
				 * 如果在 -CI-，-CE- 或 -CY- 则 S 被去掉，如果是 -SCI-， 
				 * SCE-，-SCY- (在 S 中处理) 否则为 K
				 */
			case 'C':
				if (MAKESOFT(Next_Letter))
				{				/* C[IEY] */
					if (After_Next_Letter == 'A' &&
						Next_Letter == 'I')
					{			/* CIA */
						Phonize(SH);
					}
					/* SC[IEY] */
					else if (Prev_Letter == 'S')
					{
						/* 被去掉 */
					}
					else
						Phonize('S');
				}
				else if (Next_Letter == 'H')
				{
#ifndef USE_TRADITIONAL_METAPHONE
					if (After_Next_Letter == 'R' ||
						Prev_Letter == 'S')
					{			/* Christ, School */
						Phonize('K');
					}
					else
						Phonize(SH);
#else
					Phonize(SH);
#endif
					fc_skip_letter++;
				}
				else
					Phonize('K');
				break;

				/*
				 * 如果在 -DGE-，-DGI- 或 -DGY- 则 J 否则为 T
				 */
			case 'D':
				if (Next_Letter == 'G' &&
					MAKESOFT(After_Next_Letter))
				{
					Phonize('J');
					fc_skip_letter++;
				}
				else
					Phonize('T');
				break;

				/*
				 * 如果在 -GH 中并且不是 B--GH，D--GH，-H--GH，-H---GH 
				 * 则为 F，否则如果在 -GNED，-GN 中则被去掉， 
				 * 否则如果在 -DGE-，-DGI- 或 -DGY- (在 D 中处理) 
				 * 否则如果在 -GE-，-GI，-GY 中且不是 GG 则为 J 
				 * 否则为 K
				 */
			case 'G':
				if (Next_Letter == 'H')
				{
					if (!(NOGHTOF(Look_Back_Letter(3)) ||
						  Look_Back_Letter(4) == 'H'))
					{
						Phonize('F');
						fc_skip_letter++;
					}
					else
					{
						/* 无声 */
					}
				}
				else if (Next_Letter == 'N')
				{
					if (Isbreak(After_Next_Letter) ||
						(After_Next_Letter == 'E' &&
						 Look_Ahead_Letter(3) == 'D'))
					{
						/* 被去掉 */
					}
					else
						Phonize('K');
				}
				else if (MAKESOFT(Next_Letter) &&
						 Prev_Letter != 'G')
					Phonize('J');
				else
					Phonize('K');
				break;
				/* 如果在元音前而不在 C,G,P,S,T 后则为 H */
			case 'H':
				if (isvowel(Next_Letter) &&
					!AFFECTH(Prev_Letter))
					Phonize('H');
				break;

				/*
				 * 如果在 C 后则被去掉 否则为 K
				 */
			case 'K':
				if (Prev_Letter != 'C')
					Phonize('K');
				break;

				/*
				 * 如果在 H 前则为 F 否则为 P
				 */
			case 'P':
				if (Next_Letter == 'H')
					Phonize('F');
				else
					Phonize('P');
				break;

				/*
				 * K
				 */
			case 'Q':
				Phonize('K');
				break;

				/*
				 * 如果在 -SH-，-SIO- 或 -SIA- 或 -SCHW- 中为 'sh' 
				 * 否则为 S
				 */
			case 'S':
				if (Next_Letter == 'I' &&
					(After_Next_Letter == 'O' ||
					 After_Next_Letter == 'A'))
					Phonize(SH);
				else if (Next_Letter == 'H')
				{
					Phonize(SH);
					fc_skip_letter++;
				}
#ifndef USE_TRADITIONAL_METAPHONE
				else if (Next_Letter == 'C' &&
						 Look_Ahead_Letter(2) == 'H' &&
						 Look_Ahead_Letter(3) == 'W')
				{
					Phonize(SH);
					fc_skip_letter += 2;
				}
#endif
				else
					Phonize('S');
				break;

				/*
				 * 如果在 -TIA- 或 -TIO- 中为 'sh' 否则在 H 前为 'th' 
				 * 否则为 T
				 */
			case 'T':
				if (Next_Letter == 'I' &&
					(After_Next_Letter == 'O' ||
					 After_Next_Letter == 'A'))
					Phonize(SH);
				else if (Next_Letter == 'H')
				{
					Phonize(TH);
					fc_skip_letter++;
				}
				else
					Phonize('T');
				break;
				/* F */
			case 'V':
				Phonize('F');
				break;
				/* 在元音前为 W，否则被去掉 */
			case 'W':
				if (isvowel(Next_Letter))
					Phonize('W');
				break;
				/* KS */
			case 'X':
				Phonize('K');
				if (fc_max_phonemes == 0 || Phone_Len < fc_max_phonemes)
					Phonize('S');
				break;
				/* 如果后面跟着元音则为 Y */
			case 'Y':
				if (isvowel(Next_Letter))
					Phonize('Y');
				break;
				/* S */
			case 'Z':
				Phonize('S');
				break;
				/* 没有变化 */
			case 'F':
			case 'J':
			case 'L':
			case 'M':
			case 'N':
			case 'R':
				Phonize(Curr_Letter);
				break;
			default:
				/* 什么都没有 */
				break;
		}						/* 结束开关 */

		fc_w_idx += fc_skip_letter;
	}							/* 结束循环 */

	End_Phoned_Word;
}								/* 结束元音化 */


/*
 * SQL 函数： soundex(text) 返回 text
 */
PG_FUNCTION_INFO_V1(soundex);

Datum soundex(PG_FUNCTION_ARGS)
{
	char		fc_outstr[SOUNDEX_LEN + 1];
	char	   *fc_arg;

	fc_arg = text_to_cstring(PG_GETARG_TEXT_PP(0));

	fc__soundex(fc_arg, fc_outstr);

	PG_RETURN_TEXT_P(cstring_to_text(fc_outstr));
}

static void fc__soundex(const char *fc_instr, char *fc_outstr)
{
	int			fc_count;

	AssertArg(fc_instr);
	AssertArg(fc_outstr);

	/* 跳过前导的非字母字符 */
	while (*fc_instr && !isalpha((unsigned char) *fc_instr))
		++fc_instr;

	/* 如果没有剩余字符串，返回全零缓冲区 */
	if (!*fc_instr)
	{
		memset(fc_outstr, '\0', SOUNDEX_LEN + 1);
		return;
	}

	/* 按原样取第一个字母 */
	*fc_outstr++ = (char) toupper((unsigned char) *fc_instr++);

	fc_count = 1;
	while (*fc_instr && fc_count < SOUNDEX_LEN)
	{
		if (isalpha((unsigned char) *fc_instr) &&
			fc_soundex_code(*fc_instr) != fc_soundex_code(*(fc_instr - 1)))
		{
			*fc_outstr = fc_soundex_code(*fc_instr);
			if (*fc_outstr != '0')
			{
				++fc_outstr;
				++fc_count;
			}
		}
		++fc_instr;
	}

	/* 用0填充 */
	while (fc_count < SOUNDEX_LEN)
	{
		*fc_outstr = '0';
		++fc_outstr;
		++fc_count;
	}

	/* 并以null结尾 */
	*fc_outstr = '\0';
}

PG_FUNCTION_INFO_V1(difference);

Datum difference(PG_FUNCTION_ARGS)
{
	char		fc_sndx1[SOUNDEX_LEN + 1],
				fc_sndx2[SOUNDEX_LEN + 1];
	int			fc_i,
				fc_result;

	fc__soundex(text_to_cstring(PG_GETARG_TEXT_PP(0)), fc_sndx1);
	fc__soundex(text_to_cstring(PG_GETARG_TEXT_PP(1)), fc_sndx2);

	fc_result = 0;
	for (fc_i = 0; fc_i < SOUNDEX_LEN; fc_i++)
	{
		if (fc_sndx1[fc_i] == fc_sndx2[fc_i])
			fc_result++;
	}

	PG_RETURN_INT32(fc_result);
}
